﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Df.Common.Forms.Validation;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Linq;

namespace Df.Common.Forms.JsonFormatter
{
    public class ElementConverter : JsonConverter
    {
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {

            var element = value as DfBaseElement;
            var properties = element.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public)
                .Where(p => !Attribute.IsDefined(p, typeof(JsonIgnoreAttribute)));

            writer.WriteStartObject();

            writer.WritePropertyName("Type");
            serializer.Serialize(writer, element.GetType().Name);

            foreach (var property in properties)
            {
                //if (property.PropertyType == element.CurrentMode.GetType())
                //{
                //    writer.WritePropertyName(property.Name);
                //    var val = ((AccessMode)property.GetValue(element)).ToString();//  .GetValue(element);
                //    serializer.Serialize(writer, val);
                //}

                if (Attribute.IsDefined(property, typeof(JsonConverterAttribute)))
                {
                    var jsonAttr = property.GetCustomAttribute<JsonConverterAttribute>(false);
                    if (jsonAttr.ConverterType == typeof(StringEnumConverter))
                    {
                        writer.WritePropertyName(property.Name);
                        serializer.Serialize(writer, Enum.ToObject(property.PropertyType, property.GetValue(element)).ToString());
                    }

                }              
                else
                {
                    writer.WritePropertyName(property.Name);
                    serializer.Serialize(writer, property.GetValue(element));
                }

            }

            writer.WriteEndObject();

        }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            DfBaseElement DfElement;
            JObject jsonObject = JObject.Load(reader);
            return GetDfElement(jsonObject);
        }

        public override bool CanConvert(Type objectType)
        {
            return typeof(DfBaseElement).IsAssignableFrom(objectType);
        }

        #region private

        internal DfBaseElement GetDfElement(JObject jsonObject)
        {
            DfBaseElement DfElement;
            var properties = jsonObject.Properties().ToList();
            var elementType = (string)properties.Single(p => p.Name == "Type").Value;
            if (elementType == typeof(DfTextBox).Name)
            {
                DfElement = new DfTextBox();
                (DfElement as DfTextBox).Value = (string)properties.Single(p => p.Name == "Value").Value;
                if (properties.Any(p => p.Name == "PlaceHolder"))
                    (DfElement as DfTextBox).PlaceHolder = (string)properties.Single(p => p.Name == "PlaceHolder").Value;
                else
                    (DfElement as DfTextBox).PlaceHolder = string.Empty;
            }
            else if (elementType == typeof(DfNumber).Name)
            {
                DfElement = new DfNumber();
                (DfElement as DfNumber).Value = (string)properties.Single(p => p.Name == "Value").Value;
                if (properties.Any(p => p.Name == "PlaceHolder"))
                    (DfElement as DfNumber).PlaceHolder = (string)properties.Single(p => p.Name == "PlaceHolder").Value;
                else
                    (DfElement as DfNumber).PlaceHolder = string.Empty;

                if (properties.Any(p => p.Name == "MinVal") && !string.IsNullOrEmpty((string)properties.Single(p => p.Name == "MinVal").Value))
                    (DfElement as DfNumber).MinVal = (int)properties.Single(p => p.Name == "MinVal").Value;

                if (properties.Any(p => p.Name == "MaxVal") && !string.IsNullOrEmpty((string)properties.Single(p => p.Name == "MaxVal").Value))
                    (DfElement as DfNumber).MaxVal = (int)properties.Single(p => p.Name == "MaxVal").Value;
            }
            else if (elementType == typeof(DfEmail).Name)
            {
                DfElement = new DfEmail();
                (DfElement as DfEmail).Value = (string)properties.Single(p => p.Name == "Value").Value;
                if (properties.Any(p => p.Name == "PlaceHolder"))
                    (DfElement as DfEmail).PlaceHolder = (string)properties.Single(p => p.Name == "PlaceHolder").Value;
                else
                    (DfElement as DfEmail).PlaceHolder = string.Empty;
            }
            else if (elementType == typeof(DfCheckBox).Name)
            {
                DfElement = new DfCheckBox();
                (DfElement as DfCheckBox).Value = (bool)properties.Single(p => p.Name == "Value").Value;
            }
            else if (elementType == typeof(DfDropDown).Name)
            {
                DfElement = new DfDropDown();
                (DfElement as DfDropDown).Items =
                    properties.Single(p => p.Name == "Items").Value.ToObject<List<DfElementItem>>();

            }
            else if (elementType == typeof(DfCheckBoxList).Name)
            {
                DfElement = new DfCheckBoxList();
                (DfElement as DfCheckBoxList).Items =
                      properties.Single(p => p.Name == "Items").Value.ToObject<List<DfSelectedItem>>();

            }
            else if (elementType == typeof(DfSection).Name)
            {
                DfElement = new DfSection();
                var innerElements = properties.Single(p => p.Name == "Elements").Value.ToObject<List<object>>();
                foreach (var element in innerElements)
                {
                    (DfElement as DfSection).Elements.Add(GetDfElement(element as JObject));
                }
            }
            else if (elementType == typeof(DfTextArea).Name)
            { DfElement = new DfTextArea(); }
            else if (elementType == typeof(DfDatePicker).Name)
            { DfElement = new DfDatePicker(); }
            else if (elementType == typeof(DfSignature).Name)
            { DfElement = new DfSignature(); }
            else
            {
                DfElement = new DfBaseElement();
            }

            if (DfElement is IInputElement)
            {
                //if (properties.Any(p => p.Name == "IsRequired"))
                //    (DfElement as IInputElement).IsRequired =
                //        (bool)properties.Single(p => p.Name == "IsRequired").Value;
                //else
                //    (DfElement as IInputElement).IsRequired = false;
            }

            foreach (var property in properties)
            {
                if (property.Name.Equals("Type"))
                    continue;
                var proInfo = DfElement.GetType().GetProperty(property.Name);
                if (proInfo == null)
                    continue;
                if (proInfo.PropertyType == DfElement.AccessRole.GetType())
                {
                    var accessRoles = property.Value.ToObject<Dictionary<string, AccessMode>>();
                    proInfo.SetValue(DfElement, accessRoles);
                }
                else if (proInfo.PropertyType == DfElement.Validations.GetType())
                {
                    var validations = property.Value.ToObject<List<BaseElementValidator>>();
                    proInfo.SetValue(DfElement, validations);
                }
                else if (Attribute.IsDefined(proInfo, typeof(JsonConverterAttribute)))
                {
                    var jsonAttr = proInfo.GetCustomAttribute<JsonConverterAttribute>(false);
                    if (jsonAttr.ConverterType == typeof(StringEnumConverter))
                    {
                        var mode = property.Value.ToObject(proInfo.PropertyType);
                        proInfo.SetValue(DfElement, Enum.ToObject(proInfo.PropertyType, mode));
                    }

                }
                else
                {
                    if (typeof(System.Collections.IList).IsAssignableFrom(proInfo.PropertyType))
                        continue;
                    if (!string.IsNullOrEmpty((string)property.Value))
                        proInfo.SetValue(DfElement, Convert.ChangeType(property.Value, proInfo.PropertyType));
                }

            }
            return DfElement;
        }

        #endregion
    }
}
